package com.wsoft.bpm.node;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;

/**
 * xml节点
 *
 * @Author zhonglj
 * @Date 2024-09-10 16:20
 */
@Getter
@Setter
public class ActivityXml {
    @Schema(hidden = true)
    private String tagName;
    @Schema(hidden = true)
    private HashMap<String, String> xmlKeyMap;

    public void setTagName(String tagName) {
        boolean hasNameSpace = StrUtil.contains(tagName, ":");
        if (hasNameSpace) {
            this.tagName = tagName;
        } else {
            this.tagName = "bpmn:" + tagName;
        }
    }

    public ActivityXml() {
        Field[] declaredFields = this.getClass().getDeclaredFields();
        if (declaredFields != null && declaredFields.length > 0) {
            xmlKeyMap = new HashMap<>(declaredFields.length);
            for (Field f : declaredFields) {
                xmlKeyMap.put(f.getName(), f.getName());
            }
        }
    }

    public String convert() throws ParserConfigurationException, TransformerException, ClassNotFoundException {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document doc = docBuilder.newDocument();

        generateXml(doc, doc, this);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        StringWriter writer = new StringWriter();
        transformer.transform(new DOMSource(doc), new StreamResult(writer));
        return writer.getBuffer().toString();
    }

    private void generateXml(Document doc, Node lastDocNode, ActivityXml curActivityXml) throws ClassNotFoundException {
        if (curActivityXml == null) {
            return;
        }

        Element curDocNode = doc.createElement(curActivityXml.getTagName());
        Field[] declaredFields = curActivityXml.getClass().getDeclaredFields();
        if (declaredFields != null && declaredFields.length > 0) {
            for (Field field : declaredFields) {
                if (curActivityXml.skip(field)) {
                    continue;
                }

                if (field.getType().equals(TextNode.class)) {
                    TextNode textNode = (TextNode) ReflectUtil.getFieldValue(curActivityXml, field);
                    Text text = doc.createTextNode(textNode.getText());
                    curDocNode.appendChild(text);
                    lastDocNode.appendChild(curDocNode);
                    continue;
                }

                if (isSubClass(field.getType(), ActivityXml.class)) {
                    ActivityXml nextXml = (ActivityXml) ReflectUtil.getFieldValue(curActivityXml, field);
                    generateXml(doc, curDocNode, nextXml);
                    continue;
                }

                if (isSubClass(field.getType(), Collection.class)) {
                    // 获取列表泛型类型
                    Type actualType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
                    Class<?> actualClass = Class.forName(actualType.getTypeName());
                    if (isSubClass(actualClass, ActivityXml.class)) {
                        Collection<ActivityXml> collection = (Collection<ActivityXml>) ReflectUtil.getFieldValue(curActivityXml, field);
                        if (CollUtil.isNotEmpty(collection)) {
                            for (ActivityXml nextXml : collection) {
                                generateXml(doc, curDocNode, nextXml);
                                lastDocNode.appendChild(curDocNode);
                            }
                        }

                    }
                    continue;
                }


                Object fieldValue = ReflectUtil.getFieldValue(curActivityXml, field);
                String attrValue = curActivityXml.getXmlKeyMap().get(field.getName());
                if (StrUtil.isNotBlank(attrValue) && fieldValue != null) {
                    curDocNode.setAttribute(attrValue, fieldValue.toString());
                }

            }

        }

        addEmptyTextNodeIfSelfClosing(curDocNode, doc);
        lastDocNode.appendChild(curDocNode);
    }

    /**
     * 判断元素是否自闭合，如果是，添加一个空文本节点
     *
     * @param element
     * @param doc
     * @return void
     * @author zhonglj
     * @date 2025/1/9 16:08
     **/
    private void addEmptyTextNodeIfSelfClosing(Element element, Document doc) {
        // 如果没有子节点，认为是自闭合标签
        if (element.getChildNodes().getLength() == 0) {
            Text textNode = doc.createTextNode(" ");
            // 添加一个空文本节点
            element.appendChild(textNode);
        }
    }

    public String putXmlKey(String key, String value) {
        if (xmlKeyMap == null) {
            return null;
        }

        return xmlKeyMap.put(key, value);
    }

    /**
     * 判断指定字段是否跳过
     *
     * @param field
     * @return java.lang.Boolean
     * @author zhonglj
     * @date 2024-09-11 10:10
     **/
    public Boolean skip(Field field) {
        return Boolean.FALSE;
    }

    /**
     * 判断一个类是否是另一个类的子类
     *
     * @param subClass   子类
     * @param superClass 父类
     * @return 如果 subClass 是 superClass 的子类，返回 true；否则返回 false
     */
    private boolean isSubClass(Class<?> subClass, Class<?> superClass) {
        return superClass.isAssignableFrom(subClass);
    }
}
